package com.newsee.remote.common.utils.http;

import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.*;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.*;
import org.apache.http.client.utils.URLEncodedUtils;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.entity.mime.HttpMultipartMode;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.entity.mime.content.ContentBody;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/**
 * HttpClient
 */
@Component
@Slf4j
public class HttpClientTemplate {
    private Integer defaultTimeout;

    private String defaultCharset;

    private PoolingHttpClientConnectionManager connectionManager;

    private HttpClient httpClient;

    /**
     * 初始化
     */
    @PostConstruct
    public void init() {
        httpClient = HttpClients.custom().setConnectionManager(connectionManager).setConnectionManagerShared(true)
            .build();
    }

    /**
     * 执行GET请求
     *
     * @param url
     *            请求地址
     * @return 响应数据
     * @throws IOException
     *             异常
     */
    public String doGet(String url) throws IOException {
        return doGet(url, null);
    }

    /**
     * 执行GET请求
     *
     * @param url
     *            请求地址
     * @param params
     *            请求参数
     * @return 响应数据
     * @throws IOException
     *             异常
     */
    public String doGet(String url, Map<String, String> params) throws IOException {
        return doGet(url, params, null, null);
    }

    /**
     * 执行GET请求
     *
     * @param url
     *            请求地址
     * @param params
     *            请求参数
     * @param charset
     *            字符集
     * @param timeout
     *            超时时间
     * @return 响应数据
     * @throws IOException
     *             异常
     */
    public String doGet(String url, Map<String, String> params, String charset, Integer timeout) throws IOException {
        // 设置default值
        if (StringUtils.isBlank(charset)) {
            charset = defaultCharset;
        }
        if (timeout == null) {
            timeout = defaultTimeout;
        }

        // 请求日志
        log.debug("url={},params(toJson)={},charset={},timeout={}", url, JSON.toJSONString(params), charset,
            timeout);

        // 参数解析
        Object param = paramConvertForGet(url, params, charset);

        // 生成请求
        HttpRequestBase request = buildHttpRequest(HttpMethod.GET, url, param, timeout);

        // 执行请求
        return doRequest(request, charset);
    }

    /**
     * 执行下载GET请求
     * 
     * @param url
     *            请求地址
     * @param key
     *            请求参数
     * @return 响应数据
     * @throws IOException
     *             异常
     */
    public byte[] doGetDownload(String url, String key) throws IOException {
        // 请求日志
        log.debug("url={},key={}", url, key);

        // 参数解析,兼容跨系统url
        String fullUrl;
        if (key.indexOf('/') == -1) {
            fullUrl = MessageFormat.format("{0}/{1}", url, key);
        } else {
            fullUrl = key;
        }

        // 生成请求
        HttpRequestBase request = buildHttpRequest(HttpMethod.GET, fullUrl,
            null, null);

        // 执行请求
        return doRequestDownload(request);
    }

    /**
     * 执行POST请求
     *
     * @param url
     *            请求地址
     * @param params
     *            请求参数
     * @return 响应数据
     * @throws IOException
     *             异常
     */
    public String doPost(String url, Map<String, String> params) throws IOException {
        return doPost(url, params, null, null);
    }

    /**
     * 执行POST请求
     *
     * @param url
     *            请求地址
     * @param params
     *            参数
     * @param charset
     *            字符集
     * @param timeout
     *            超时时间
     * @return 响应数据
     * @throws IOException
     *             异常
     */
    public String doPost(String url, Map<String, String> params, String charset, Integer timeout) throws IOException {
        // 设置default值
        if (StringUtils.isBlank(charset)) {
            charset = defaultCharset;
        }
        if (timeout == null) {
            timeout = defaultTimeout;
        }

        // 请求日志
        log.debug("url={},params(toJson)={},charset={},timeout={}", url, JSON.toJSONString(params), charset,
            timeout);

        // 参数解析
        Object param = paramConvertForPost(params, charset);

        // 生成请求
        HttpRequestBase request = buildHttpRequest(HttpMethod.POST, url, param, timeout);

        // 执行请求
        return doRequest(request, charset);
    }

    /**
     * 执行POST请求
     *
     * @param url
     *            请求地址
     * @param headers
     *            请求头
     * @param params
     *            参数
     * @param charset
     *            字符集
     * @param timeout
     *            超时时间
     * @return 响应数据
     * @throws IOException
     *             异常
     */
    public String doPost(String url, Map<String, String> headers,
        Map<String, String> params, String charset, Integer timeout)
        throws IOException {
        // 设置default值
        if (StringUtils.isBlank(charset)) {
            charset = defaultCharset;
        }
        if (timeout == null) {
            timeout = defaultTimeout;
        }

        // 请求日志
        log.debug(
            "url={},headers={} params(toJson)={},charset={},timeout={}", url,
            JSON.toJSONString(headers), JSON.toJSONString(params), charset,
            timeout);

        // 参数解析
        Object param = paramConvertForPost(params, charset);

        // 生成请求
        HttpRequestBase request = buildHttpRequest(HttpMethod.POST, url, param,
            timeout);
        if (headers != null) {
            for (Map.Entry<String, String> entry: headers.entrySet()) {
                request.setHeader(entry.getKey(), entry.getValue());
            }
        }

        // 执行请求
        return doRequest(request, charset);
    }
    
    /**
     * 执行JSON格式的POST请求
     *
     * @param url
     *            POST地址
     * @param data
     *            JSON对象
     * @return 响应数据
     * @throws IOException
     *             异常
     */
    public String doPostJson(String url, Object data) throws IOException {
        return doPostJson(url, data, null, null);
    }

    /**
     * 执行JSON格式的POST请求
     *
     * @param url
     *            POST地址
     * @param data
     *            JSON对象
     * @param charset
     *            字符集
     * @param timeout
     *            超时时间
     * @return 响应数据
     * @throws IOException
     *             异常
     */
    public String doPostJson(String url, Object data, String charset, Integer timeout) throws IOException {
        // 设置default值
        if (StringUtils.isBlank(charset)) {
            charset = defaultCharset;
        }
        if (timeout == null) {
            timeout = defaultTimeout;
        }

        // 请求日志
        log.debug("url={},data(toJson)={},charset={},timeout={}", url, JSON.toJSONString(data), charset, timeout);

        // 参数解析
        Object param = paramConvertForPostJson(data);

        // 生成请求
        HttpRequestBase request = buildHttpRequest(HttpMethod.POST, url, param, timeout);

        // 执行请求
        return doRequest(request, charset);
    }

    /**
     * 执行上传的POST请求
     * 
     * @param url
     *            POST地址
     * @param data
     *            流数据
     * @return 响应数据
     * @throws IOException
     *             异常
     */
    public String doPostUpload(String url, Object data) throws IOException {
        return doPostUpload(url, data, null, null);
    }

    /**
     * 执行上传的POST请求
     * 
     * @param url
     *            POST地址
     * @param data
     *            流数据
     * @param charset
     *            字符集
     * @param timeout
     *            超时时间
     * @return 响应数据
     * @throws IOException
     *             异常
     */
    public String doPostUpload(String url, Object data, String charset, Integer timeout) throws IOException {
        // 设置default值
        if (StringUtils.isBlank(charset)) {
            charset = defaultCharset;
        }
        if (timeout == null) {
            timeout = defaultTimeout;
        }

        // 请求日志
        log.debug("url={},data(toJson)={},charset={},timeout={}", url, JSON.toJSONString(data), charset, timeout);

        // 参数解析
        Object param = paramConvertForPostUpload(data);

        // 生成请求
        HttpRequestBase request = buildHttpRequest(HttpMethod.POST, url, param, timeout);

        // 执行请求
        return doRequest(request, charset);
    }

    /**
     * 为GET请求解析参数
     *
     * @param url
     *            请求地址
     * @param params
     *            请求参数
     * @param charset
     *            字符集
     * @return 格式化请求参数
     */
    private String paramConvertForGet(String url, Map<String, String> params, String charset) {
        // 解析参数
        String paramStr = StringUtils.EMPTY;
        if (MapUtils.isNotEmpty(params)) {
            paramStr = URLEncodedUtils.format(paramConvert(params), charset);
        }
        if (StringUtils.isNotBlank(paramStr)) {
            if (!url.contains("?")){
                paramStr = "?" + paramStr;
            }
            else{
                paramStr = "&" + paramStr;

            }
        }
        return paramStr;
    }


    /**
     * 为GET请求解析为表单参数
     *
     * @param params  请求参数
     * @param charset 字符集
     * @return 格式化请求参数
     */
    private UrlEncodedFormEntity paramConvertEntityForGet(Map<String, String> params, String charset) throws UnsupportedEncodingException {
        List<BasicNameValuePair> pairList = new ArrayList<>();
        params.forEach((key, value) -> {
            BasicNameValuePair nameValuePair = new BasicNameValuePair(key, value);
            pairList.add(nameValuePair);
        });
        return new UrlEncodedFormEntity(pairList, charset);
    }

    /**
     * 为POST请求解析参数
     *
     * @param params
     *            请求参数
     * @param charset
     *            字符集
     * @return 格式化请求参数
     * @throws IOException
     *             异常
     */
    private HttpEntity paramConvertForPost(Map<String, String> params, String charset) throws IOException {
        UrlEncodedFormEntity entity = null;
        if (MapUtils.isNotEmpty(params)) {
            entity = new UrlEncodedFormEntity(paramConvert(params), charset);
        }
        return entity;
    }

    /**
     * 为POST JSON请求解析参数
     *
     * @param param
     *            请求参数
     * @return 格式化请求参数
     */
    private HttpEntity paramConvertForPostJson(Object param) {
        StringEntity entity = null;
        if (param != null) {
            entity = new StringEntity(JSON.toJSONString(param), ContentType.APPLICATION_JSON);
        }
        return entity;
    }

    /**
     * 为POST UPLOAD请求解析参数
     * 
     * @param param
     *            请求参数
     * @return 格式化请求参数
     */
    private HttpEntity paramConvertForPostUpload(Object param) {
        MultipartEntityBuilder builder = MultipartEntityBuilder.create();
        builder.addPart("request", (ContentBody) param);
        builder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE);
        return builder.build();
    }

    /**
     * 生成请求
     *
     * @param method
     *            请求方法
     * @param url
     *            请求地址
     * @param param
     *            请求参数
     * @param timeout
     *            超时时间
     * @return 请求
     */
    private HttpRequestBase buildHttpRequest(HttpMethod method, String url, Object param, Integer timeout) {
        HttpRequestBase request;
        switch (method) {
            case GET:
                String urlWithParam = url;
                if (param != null && param instanceof String) {
                    urlWithParam += (String) param;
                }
                request = new HttpGet(urlWithParam);
                break;
            case POST:
            default:
                request = new HttpPost(url);
                if (param != null && param instanceof HttpEntity) {
                    ((HttpPost) request).setEntity((HttpEntity) param);
                }
                break;
        }
        if (timeout != null) {
            RequestConfig config = RequestConfig.custom().setConnectTimeout(timeout)
                .setConnectionRequestTimeout(timeout).setSocketTimeout(timeout).setRedirectsEnabled(false).build();
            request.setConfig(config);
        }
        return request;
    }

    /**
     * 执行request请求
     *
     * @param request
     *            请求
     * @param charset
     *            字符集
     * @return 响应数据
     * @throws IOException
     *             异常
     */
    private String doRequest(HttpUriRequest request, String charset) throws IOException {
        // 执行请求

        HttpResponse response = httpClient.execute(request);

        // 解析请求返回
        String responseStr = StringUtils.EMPTY;
        StatusLine statusLine = response.getStatusLine();
        if (statusLine == null) {
            throw new IOException("http status not specified");
        }
        Integer httpStatus = statusLine.getStatusCode();
        if (!Objects.equals(httpStatus, HttpStatus.SC_OK)) {
            EntityUtils.consumeQuietly(response.getEntity());
            throw new IOException("http status: " + httpStatus);
        }
        HttpEntity entity = response.getEntity();
        if (entity != null) {
            responseStr = EntityUtils.toString(entity, charset);
        }
        log.debug("response(toJson)={}", JSON.toJSONString(responseStr));
        return responseStr;
    }

    /**
     * 执行request请求
     * 
     * @param request
     *            请求
     * @return 响应数据
     * @throws IOException
     *             异常
     */
    private byte[] doRequestDownload(HttpUriRequest request) throws IOException {
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();

        // 获取HTTP client
        CloseableHttpClient httpClient = HttpClients.createDefault();
        try {
            // 执行请求
            CloseableHttpResponse response = httpClient.execute(request);
            try {
                // 解析请求返回
                StatusLine statusLine = response.getStatusLine();
                if (statusLine == null) {
                    throw new IOException("http status not specified");
                }
                Integer httpStatus = statusLine.getStatusCode();
                if (!Objects.equals(httpStatus, HttpStatus.SC_OK)) {
                    EntityUtils.consumeQuietly(response.getEntity());
                    throw new IOException("http status: " + httpStatus);
                }

                // 读取数据
                HttpEntity entity = response.getEntity();
                if (entity != null) {
                    InputStream inputStream = entity.getContent();
                    byte[] buffer = new byte[4096];
                    int size;
                    while ((size = inputStream.read(buffer)) > 0) {
                        outputStream.write(buffer, 0, size);
                    }
                    outputStream.flush();
                    outputStream.close();
                    EntityUtils.consume(entity);
                }
            } finally {
                response.close();
            }
        } finally {
            httpClient.close();
        }
        return outputStream.toByteArray();
    }

    /**
     * 参数转换
     *
     * @param params
     *            参数
     * @return 键值对
     */
    private List<NameValuePair> paramConvert(Map<String, String> params) {
        List<NameValuePair> formParams = new ArrayList<>();
        if (MapUtils.isNotEmpty(params)) {
            for (Map.Entry<String, String> entry: params.entrySet()) {
                formParams.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
            }
        }
        return formParams;
    }

    public PoolingHttpClientConnectionManager getConnectionManager() {
        return connectionManager;
    }

    public void setConnectionManager(PoolingHttpClientConnectionManager connectionManager) {
        this.connectionManager = connectionManager;
    }

    public Integer getDefaultTimeout() {
        return defaultTimeout;
    }

    public void setDefaultTimeout(Integer defaultTimeout) {
        this.defaultTimeout = defaultTimeout;
    }

    public String getDefaultCharset() {
        return defaultCharset;
    }

    public void setDefaultCharset(String defaultCharset) {
        this.defaultCharset = defaultCharset;
    }


    public String doGet(String url, Map<String, String> params, Map<String, String> headers) throws IOException{

        // 请求日志
        log.debug("url={},params(toJson)={}", url, JSON.toJSONString(params));

        // 参数解析
        Object param = paramConvertForGet(url, params, null);

        // 生成请求
        HttpRequestBase request = buildHttpRequest(HttpMethod.GET, url, param, null);
        if (headers != null) {
            for (Map.Entry<String, String> entry: headers.entrySet()) {
                request.setHeader(entry.getKey(), entry.getValue());
            }
        }
        // 执行请求
        return doRequest(request, null );
    }

}
